package org.xx.armory.db.impl;

import org.xx.armory.db.MatchType;
import org.xx.armory.db.Parameter;
import org.xx.armory.db.ParameterDirection;
import org.xx.armory.db.ParameterType;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.ResultSet;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Pattern;

import static org.apache.commons.lang3.ArrayUtils.subarray;
import static org.apache.commons.lang3.BooleanUtils.toBoolean;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.apache.commons.lang3.math.NumberUtils.isCreatable;
import static org.apache.commons.lang3.math.NumberUtils.toDouble;
import static org.apache.commons.lang3.math.NumberUtils.toLong;
import static org.apache.commons.lang3.time.DateFormatUtils.format;
import static org.xx.armory.commons.Converter.formatHexString;
import static org.xx.armory.commons.Converter.parseDate;
import static org.xx.armory.commons.Converter.toDate;
import static org.xx.armory.commons.DateTimeUtils.copy;
import static org.xx.armory.commons.DateTimeUtils.daysOfMonth;
import static org.xx.armory.commons.DateTimeUtils.maxValue;
import static org.xx.armory.commons.DateTimeUtils.minValue;
import static org.xx.armory.commons.DateTimeUtils.toLocal;
import static org.xx.armory.commons.Validators.notBlank;
import static org.xx.armory.commons.Validators.notNull;

/**
 * 基本的SQL查询参数实现。
 *
 * @author Haart
 */
final class SimpleParameter
        implements Parameter {
    /**
     *
     */
    private static final String ISO8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ss Z";

    /**
     *
     */
    private static final Pattern PERCENT_SYMBOL = Pattern.compile("%");

    private final String name;
    private final ParameterType type;
    private final Set<ParameterDirection> directions;
    private final MatchType match;
    private Object value;

    /**
     * 构造{@code BasicParameter}实例。
     *
     * @param name
     *         参数名。
     * @param type
     *         参数类型。
     * @param directions
     *         参数传入传出方式。
     * @param match
     *         参数匹配方式。
     * @param value
     *         参数值。
     * @throws IllegalArgumentException
     *         如果参数{@code type}是{@code null}或者参数{@code match}是{@code null}或者参数{@code name}只包含空白字符。
     */
    SimpleParameter(
            String name,
            ParameterType type,
            Set<ParameterDirection> directions,
            MatchType match,
            Object value
    ) {
        notNull(type, "type");
        notNull(match, "match");

        this.name = notBlank(name, "name").trim();
        this.type = type;
        this.match = match;
        this.directions = EnumSet.copyOf(directions);
        this.value = this.normalizeValue(value);
    }

    /**
     * 去掉首尾空格。
     *
     * @param v
     *         原始值。
     * @return 去掉首尾空格之后的值，如果参数{@code v}是{@code null}则返回空字符串。
     * @see Object#toString();
     */
    private static String trimValue(Object v) {
        return v != null ? trimToEmpty(v.toString()) : "";
    }

    /**
     * 添加通配符。
     *
     * @param s
     *         原始字符串。
     * @param type
     *         匹配方式。
     * @return 添加了通配符的字符串。
     */
    private static String addWildChars(
            String s,
            MatchType type
    ) {
        if (s == null) {
            s = "";
        }

        final StringBuilder buf = new StringBuilder(PERCENT_SYMBOL.matcher(s).replaceAll("%%"));
        switch (type) {
            case STARTS_WITH:
                buf.append("%");
                break;
            case ENDS_WITH:
                buf.insert(0, "%");
                break;
            case CONTAINS:
                if (buf.length() != 0) {
                    buf.insert(0, "%");
                }
                buf.append("%");
                break;
            default:
                throw new IllegalArgumentException("illegal match type: " + type);
        }

        return buf.toString();
    }

    /**
     * 截断日期。
     *
     * @param d
     *         待截断的日期，如果此参数是{@code null}则使用默认值，起始日期的默认值是最小日期，终止日期的默认值是最大日期。
     * @param type
     *         日期参数的类型。
     * @return 截断后的日期。
     * @throws IllegalArgumentException
     *         如果参数{@code type}是{@code null}。
     */
    private static Date truncateDate(
            Date d,
            ParameterType type
    ) {
        if (type == ParameterType.DATETIME) {
            return d;
        }

        if (d == null) {
            switch (type) {
                case START_YEAR:
                case START_MONTH:
                case START_DATE:
                    return minValue();
                case END_YEAR:
                case END_MONTH:
                case END_DATE:
                    return maxValue();
                default:
                    throw new IllegalArgumentException("illegal type: " + type);
            }
        } else {
            Calendar c = Calendar.getInstance();
            c.setTime(d);
            if (!c.getTimeZone().equals(TimeZone.getDefault())) {
                c = toLocal(c);
            }

            final int year = c.get(Calendar.YEAR);
            final int month = c.get(Calendar.MONTH);

            switch (type) {
                case START_DATE:
                    return copy(c, -1, -1, -1, 0, 0, 0).getTime();
                case START_MONTH:
                    return copy(c, -1, -1, 1, 0, 0, 0).getTime();
                case START_YEAR:
                    return copy(c, -1, 0, 1, 0, 0, 0).getTime();
                case END_DATE:
                    return copy(c, -1, -1, -1, 23, 59, 59, 999).getTime();
                case END_MONTH:
                    return copy(c, -1, -1, daysOfMonth(year, month), 23, 59, 59, 999).getTime();
                case END_YEAR:
                    return copy(c, -1, 11, 31, 23, 59, 59, 999).getTime();
                default:
                    throw new IllegalArgumentException("illegal type: " + type);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getName() {
        return this.name;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ParameterType getParameterType() {
        return this.type;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<ParameterDirection> getDirections() {
        return Collections.unmodifiableSet(this.directions);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object getValue() {
        return this.value;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setValue(Object value) {
        this.value = normalizeValue(value);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isNull() {
        return this.value == null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isEmpty() {
        return this.value != null && String.valueOf(this.value).isEmpty();
    }

    /**
     * 按照参数类型将参数值进行正规化，比如将字符串解析为数值。
     *
     * @param value
     *         原始值。
     * @return 正规化以后的值，必须是和{@link #getParameterType()}匹配的类型或者是{@code null}，
     */
    private Object normalizeValue(Object value) {
        if (value == null) {
            if (this.type == ParameterType.STRING) {
                value = "";
            } else {
                return null;
            }
        }

        String s;
        switch (this.type) {
            case STRING:
                s = value.toString();
                return this.match == MatchType.EXACT ? s : addWildChars(s, this.match);
            case INTEGER:
                if (value instanceof Number) {
                    return ((Number) value).longValue();
                } else {
                    return toLong(trimValue(value));
                }
            case DECIMAL:
                if (value instanceof BigDecimal) {
                    return value;
                } else if (value instanceof BigInteger) {
                    return new BigDecimal((BigInteger) value);
                } else {
                    s = trimValue(value);
                    return isCreatable(s) ? new BigDecimal(s) : null;
                }
            case DATETIME:
            case START_DATE:
            case START_MONTH:
            case START_YEAR:
            case END_DATE:
            case END_MONTH:
            case END_YEAR:
                final Date d;
                if (value instanceof Date) {
                    d = (Date) value;
                } else if (value instanceof Calendar) {
                    d = ((Calendar) value).getTime();
                } else if (value instanceof Number) {
                    d = toDate((Number) value);
                } else if (value instanceof String) {
                    d = parseDate(trimValue(value));
                } else {
                    d = null;
                }

                return d != null ? truncateDate(d, this.type) : null;
            case DOUBLE:
                if (value instanceof Number) {
                    return ((Number) value).doubleValue();
                } else {
                    return toDouble(value.toString());
                }
            case BOOLEAN:
                if (value instanceof Number) {
                    return ((Number) value).longValue() != 0;
                } else if (value instanceof Boolean) {
                    return value;
                } else {
                    return toBoolean(trimValue(value));
                }
            case BINARY:
                if (value instanceof byte[]) {
                    return value;
                } else {
                    return null;
                }
            case RESULT_SET:
                // 此类型的参数仅用于传出参数。
                return value instanceof ResultSet ? value : null;
            default:
                assert false : "unknown type: " + this.type;
                return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        final Object v = getValue();
        String sv;
        if (v == null) {
            sv = "<null>";
        } else if (v instanceof Date) {
            sv = format((Date) v, ISO8601_FORMAT);
        } else if (v instanceof Calendar) {
            sv = format((Calendar) v, ISO8601_FORMAT);
        } else if (v instanceof byte[]) {
            byte[] bv = (byte[]) getValue();
            if (bv.length > 0) {
                sv = String.format("bytes[%d]{0x%s}", bv.length, formatHexString(subarray(bv, 0, 4)));
            } else {
                sv = "bytes[0]";
            }
        } else if (v instanceof CharSequence) {
            sv = "\"" + v.toString() + "\"";
        } else {
            sv = v.toString();
        }

        final Set<ParameterDirection> dirs = getDirections();
        if (dirs.contains(ParameterDirection.IN)) {
            return String.format("[%s] %s, %s, %s", getName(), getParameterType(), getDirections(), sv);
        } else {
            return String.format("[%s] %s, %s", getName(), getParameterType(), getDirections());
        }
    }
}
